package com.lijiajia.cloud.resource.sys.service.numberid;

import cn.hutool.core.util.IdUtil;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 委派 数字id 服务实现
 *
 * @author lijiajia
 * @since 2019-05-24
 */
public class DelegatingNumberIdService implements NumberIdService {
    private final List<NumberIdService> services;
    private final Map<NumberIdService, String> serviceIds = new HashMap<>();
    private final Map<String, Statistics> statisticsMap = new HashMap<>();

    public DelegatingNumberIdService(List<NumberIdService> services) {
        this.services = services;
        services.forEach(service -> {
            String serviceId = IdUtil.simpleUUID();
            serviceIds.put(service, serviceId);
            statisticsMap.put(serviceId, new Statistics());
        });
    }

    @Override
    public Number get(String name) {
        Assert.notNull(name, "name 不能为空");

        printAnalysis();
        return this.services.stream()
                // 过滤可靠服务
                .filter(service -> {
                    String serviceId = serviceIds.get(service);
                    Statistics statistics = statisticsMap.get(serviceId);
                    return statistics.isAvailable();
                })
                // 执行服务
                .map(service -> new Result<Number>()
                        .setServiceId(serviceIds.get(service))
                        .setResult(service.get(name)))
                // 服务结果统计
                .peek(result -> {
                    Statistics statistics = statisticsMap.get(result.getServiceId());
                    statistics.total += 1;
                    if (result.getResult() == null) {
                        statistics.fail += 1;
                    } else {
                        statistics.pass += 1;
                    }
                })
                // 获取结果
                .map(Result::getResult)
                .filter(Objects::nonNull)
                .findFirst()
                // 异常
                .orElseThrow(() -> new NumberIdException(String.format("无法生成Id  参数:[%s]", name)));
    }

    @Override
    public String get(String name, String prefix) {
        Assert.notNull(name, "name 不能为空");

        printAnalysis();

        return this.services.stream()
                // 过滤可靠服务
                .filter(service -> {
                    String serviceId = serviceIds.get(service);
                    Statistics statistics = statisticsMap.get(serviceId);
                    return statistics.isAvailable();
                })
                // 执行服务
                .map(service -> new Result<String>()
                        .setServiceId(serviceIds.get(service))
                        .setResult(service.get(name, prefix)))
                // 服务结果统计
                .peek(result -> {
                    Statistics statistics = statisticsMap.get(result.getServiceId());
                    statistics.total += 1;
                    if (result.getResult() == null) {
                        statistics.fail += 1;
                    } else {
                        statistics.pass += 1;
                    }
                })
                // 获取结果
                .map(Result::getResult)
                .filter(Objects::nonNull)
                .findFirst()
                // 异常
                .orElseThrow(() -> new NumberIdException(String.format("无法生成Id  参数:[%s]", name)));
    }

    @Override
    public String get(String name, String prefix, String suffix) {
        Assert.notNull(name, "name 不能为空");

        printAnalysis();

        return this.services.stream()
                // 过滤可靠服务
                .filter(service -> {
                    String serviceId = serviceIds.get(service);
                    Statistics statistics = statisticsMap.get(serviceId);
                    return statistics.isAvailable();
                })
                // 执行服务
                .map(service -> new Result<String>()
                        .setServiceId(serviceIds.get(service))
                        .setResult(service.get(name, prefix, suffix)))
                // 服务结果统计
                .peek(result -> {
                    Statistics statistics = statisticsMap.get(result.getServiceId());
                    statistics.total += 1;
                    if (result.getResult() == null) {
                        statistics.fail += 1;
                    } else {
                        statistics.pass += 1;
                    }
                })
                // 获取结果
                .map(Result::getResult)
                .filter(Objects::nonNull)
                .findFirst()
                // 异常
                .orElseThrow(() -> new NumberIdException(String.format("无法生成Id  参数:[%s]", name)));
    }

    @Override
    public void setValue(String name, Number value) {
        Assert.notNull(name, "name 不能为空");
        Assert.notNull(name, "value 不能为空");
        services.forEach(service -> service.setValue(name, value));
    }

    @Override
    public void setIncrement(String name, Number increment) {
        Assert.notNull(name, "name 不能为空");
        Assert.notNull(name, "increment 不能为空");
        services.forEach(service -> service.setValue(name, increment));
    }

    @Data
    @Accessors(chain = true)
    class Result<T> {
        String serviceId;
        T result;
    }

    @Data
    @Accessors(chain = true)
    class Statistics {
        long total = 0;
        long pass = 0L;
        long fail = 0L;

        /**
         * 服务可用性 10%容错率
         *
         * @return 服务是否可用
         */
        boolean isAvailable() {
            if (total <= 10 || fail == 0) return true;
            return pass / fail >= 10;
        }
    }

    private void printAnalysis() {
        System.out.println("-----------------");
        serviceIds.forEach((k, v) -> {
            Statistics s = statisticsMap.get(v);
            System.out.println(String.format("serviceId:%s, name:%s, total: %s, pass: %s, fail: %s", k, v.getClass().getSimpleName(), s.getTotal(), s.getPass(), s.getFail()));
        });
        System.out.println("-----------------");
    }
}
